/*
MIT License Applies
*/	

/*
Options
-----------------
showWeekends: boolean
data: object
cellWidth: number
cellHeight: number
slideWidth: number
dataUrl: string
behavior: {
	clickable: boolean,
	draggable: boolean,
	resizable: boolean,
	onClick: function,
	onDrag: function,
	onResize: function
}
*/

(function (jQuery) {

    jQuery.fn.ganttView = function () {
    	
    	var args = Array.prototype.slice.call(arguments); // call is method chaining
		var chart = new Chart();
		
    	if (args.length == 1 && typeof(args[0]) == "object") {
        	build.call(this, chart, args[0]);
    	}
    	
    	if (args.length == 2 && typeof(args[0]) == "string") {
    		handleMethod.call(this, args[0], args[1]);
    	}
		
		this.addTask = function(task){
			chart.addTask(task);
		}
		
		return{
			addTask: this.addTask
		}
    };
	
	// default config for gantt view dtat goes into options
    function build(chart, options) {
    	
    	var els = this;
        var defaults = {
			// configurations for Gantt view
            showWeekends: true,
            cellWidth: 21,
            cellHeight: 31,
            slideWidth: 400,
            vHeaderWidth: 100,
            behavior: {
            	clickable: true,
            	draggable: true,
            	resizable: true
            }
        };
        
        var opts = jQuery.extend(true, defaults, options); // copying user specifed options to default

		// the data can be retrieved by two way one is javascript or other is ajax get call.
		if (opts.data) {// so if set of data exists then call build()
			build();
		} else if (opts.dataUrl) {// retrieve data from specified url and then call build()
			jQuery.getJSON(opts.dataUrl, function (data) { opts.data = data; build(); });
		}

		function build() {
			
			var minDays = Math.floor((opts.slideWidth / opts.cellWidth)  + 5);
			var startEnd = DateUtils.getBoundaryDatesFromData(opts.data, minDays);
			opts.start = startEnd[0];
			opts.end = startEnd[1];

	        //els.each(function () {
			var container = jQuery(els);
			var div = jQuery("<div>", { "class": "ganttview" });
			//chart = new Chart(div, opts);
			chart.setDiv(div);
			chart.setOptions(opts);
			chart.render();
			
			container.append(div);
			
			var w = jQuery("div.ganttview-vtheader", container).outerWidth() +
				jQuery("div.ganttview-slide-container", container).outerWidth();
			container.css("width", (w + 2) + "px");
			
			new Behavior(container, opts).apply();
	        //});
		}
		
    }

	function handleMethod(method, value) {
		
		if (method == "setSlideWidth") {
			var div = $("div.ganttview", this);
			div.each(function () {
				var vtWidth = $("div.ganttview-vtheader", div).outerWidth();
				$(div).width(vtWidth + value + 1);
				$("div.ganttview-slide-container", this).width(value);
			});
		}
	}

	// main chart drawer
	var Chart = function(div, opts) {
		
		var monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
		var scheduleHeaderRegion; 
		var tasksRegion;
		var cellWidth;
		var start;
		var div;
		var opts;
		
		function setDiv(pDiv){
			div = pDiv;
		}
		
		function setOptions(options){
			opts = options;
			cellWidth = opts.cellWidth;
			start = opts.start;
		}
		
		// renders gantt chart
		function render() {
//			addVtHeader(div, opts.data, opts.cellHeight); //TODO this has to be done in separate way

            var slideDiv = jQuery("<div>", {
                "class": "ganttview-slide-container",
                "css": { "width": opts.slideWidth + "px" }
            });
			taskRegion = $("<div>", { "class": "ganttview-blocks" });

            dates = getDates(opts.start, opts.end);
            addHzHeader(slideDiv, dates, opts.cellWidth); // add years, months, dates to header

//            addGrid(slideDiv, opts.data, dates, opts.cellWidth, opts.showWeekends);
			
			// Initialize Task Region and add Tasks
			slideDiv.append(taskRegion);
			$.each(opts.data, function(index, value){
				addTask(value);
			});

            div.append(slideDiv);
            applyLastClass(div.parent());
		}

		// Creates a 3 dimensional array [year][month][day] of every day 
		// between the given start and end dates
        function getDates(start, end) {
            var dates = [];
			dates[start.getFullYear()] = [];
			dates[start.getFullYear()][start.getMonth()] = [start]
			var last = start;
			while (last.compareTo(end) == -1) {
				var next = last.clone().addDays(1);
				if (!dates[next.getFullYear()]) { dates[next.getFullYear()] = []; }
				if (!dates[next.getFullYear()][next.getMonth()]) { 
					dates[next.getFullYear()][next.getMonth()] = []; 
				}
				dates[next.getFullYear()][next.getMonth()].push(next);
				last = next;
			}
			return dates;
        }

        function addVtHeader(div, data, cellHeight) {
            var headerDiv = jQuery("<div>", { "class": "ganttview-vtheader" });
            for (var i = 0; i < data.length; i++) {
                var itemDiv = jQuery("<div>", { "class": "ganttview-vtheader-item" });
                itemDiv.append(jQuery("<div>", {
                    "class": "ganttview-vtheader-item-name",
                    "css": { "height": (data[i].series.length * cellHeight) + "px" }
                }).append(data[i].name));
                var seriesDiv = jQuery("<div>", { "class": "ganttview-vtheader-series" });
                for (var j = 0; j < data[i].series.length; j++) {
                    seriesDiv.append(jQuery("<div>", { "class": "ganttview-vtheader-series-name" })
						.append(data[i].series[j].name));
                }
                itemDiv.append(seriesDiv);
                headerDiv.append(itemDiv);
            }
            div.append(headerDiv);
        }

        function addHzHeader(div, dates, cellWidth) {
            var headerDiv = jQuery("<div>", { "class": "ganttview-hzheader" });
            var monthsDiv = jQuery("<div>", { "class": "ganttview-hzheader-months" });
            var daysDiv = jQuery("<div>", { "class": "ganttview-hzheader-days" });
            var totalW = 0;
			for (var y in dates) {
				for (var m in dates[y]) {
					var w = dates[y][m].length * cellWidth;
					totalW = totalW + w;
					monthsDiv.append(jQuery("<div>", {
						"class": "ganttview-hzheader-month",
						"css": { "width": (w - 1) + "px" }
					}).append(monthNames[m] + "/" + y));
					for (var d in dates[y][m]) {
						var day = dates[y][m][d].getDate();
						daysDiv.append(jQuery("<div>", { "class": "ganttview-hzheader-day" })
							.append(day));
					}
				}
			}
            monthsDiv.css("width", totalW + "px");
            daysDiv.css("width", totalW + "px");
            headerDiv.append(monthsDiv).append(daysDiv);
            div.append(headerDiv);
        }
	
		//need to be fixed! data.length not data
        function addGrid(div, data, dates, cellWidth, showWeekends) {
            var gridDiv = jQuery("<div>", { "class": "ganttview-grid" });
            var rowDiv = jQuery("<div>", { "class": "ganttview-grid-row" });
			for (var y in dates) {
				for (var m in dates[y]) {
					for (var d in dates[y][m]) {
						var cellDiv = jQuery("<div>", { "class": "ganttview-grid-row-cell" });
						if (DateUtils.isWeekend(dates[y][m][d]) && showWeekends) { 
							cellDiv.addClass("ganttview-weekend"); 
						}
						rowDiv.append(cellDiv);
					}
				}
			}
            var w = jQuery("div.ganttview-grid-row-cell", rowDiv).length * cellWidth;
            rowDiv.css("width", w + "px");
            gridDiv.css("width", w + "px");
            for (var i = 0; i < data.length; i++) {
                    gridDiv.append(rowDiv.clone());
            }
            div.append(gridDiv);
        }// addGrid()

		function addTask(task){
			var blockParent = jQuery("<div>", { "class": "ganttview-block-container" }); // init new Block Container
			var size = DateUtils.daysBetween(task.start, task.end) + 1;
			var offset = DateUtils.daysBetween(start, task.start);
			var block = jQuery("<div>", {
				"class": "ganttview-block",
				"title": task.name + ", " + size + " days",
				"css": {
					"width": ((size * cellWidth) - 9) + "px",
					"margin-left": ((offset * cellWidth) + 3) + "px"
				}
			});
			
			// adding data
			block.data("block-data", { id: task.id, name: task.name });
			//later on extend data with JQuery.extend
			block.css("background-color", "#aabbcc");
			block.append(jQuery("<div>", { "class": "ganttview-block-text" }).text(size));
			taskRegion.append(jQuery(blockParent).append(block));
		}

        function applyLastClass(div) {
            jQuery("div.ganttview-grid-row div.ganttview-grid-row-cell:last-child", div).addClass("last");
            jQuery("div.ganttview-hzheader-days div.ganttview-hzheader-day:last-child", div).addClass("last");
            jQuery("div.ganttview-hzheader-months div.ganttview-hzheader-month:last-child", div).addClass("last");
        }
		
		return {
			render: render,
			addTask: addTask,
			setDiv: setDiv,
			setOptions: setOptions
		};
	}

	var Behavior = function (div, opts) {
		
		function apply() {
			
			if (opts.behavior.clickable) { 
            	bindBlockClick(div, opts.behavior.onClick); 
        	}
        	
            if (opts.behavior.resizable) { 
            	bindBlockResize(div, opts.cellWidth, opts.start, opts.behavior.onResize); 
        	}
            
            if (opts.behavior.draggable) { 
            	bindBlockDrag(div, opts.cellWidth, opts.start, opts.behavior.onDrag); 
        	}
		}

        function bindBlockClick(div, callback) {
            jQuery("div.ganttview-block", div).live("click", function () {
                if (callback) { callback(jQuery(this).data("block-data")); }
            });
        }
        
        function bindBlockResize(div, cellWidth, startDate, callback) {
        	jQuery("div.ganttview-block", div).resizable({
        		grid: cellWidth, 
        		handles: "e,w",
        		stop: function () {
        			var block = jQuery(this);
        			updateDataAndPosition(div, block, cellWidth, startDate);
        			if (callback) { callback(block.data("block-data")); }
        		}
        	});
        }
        
        function bindBlockDrag(div, cellWidth, startDate, callback) {
        	jQuery("div.ganttview-block", div).draggable({
        		axis: "x", 
        		grid: [cellWidth, cellWidth],
        		stop: function () {
        			var block = jQuery(this);
        			updateDataAndPosition(div, block, cellWidth, startDate);
        			if (callback) { callback(block.data("block-data")); }
        		}
        	});
        }
        
        function updateDataAndPosition(div, block, cellWidth, startDate) {
        	var container = jQuery("div.ganttview-slide-container", div);
        	var scroll = container.scrollLeft();
			var offset = block.offset().left - container.offset().left - 1 + scroll;
			
			// Set new start date
			var daysFromStart = Math.round(offset / cellWidth);
			var newStart = startDate.clone().addDays(daysFromStart);
			block.data("block-data").start = newStart;

			// Set new end date
        	var width = block.outerWidth();
			var numberOfDays = Math.round(width / cellWidth) - 1;
			block.data("block-data").end = newStart.clone().addDays(numberOfDays);
			jQuery("div.ganttview-block-text", block).text(numberOfDays + 1);
			
			// Remove top and left properties to avoid incorrect block positioning,
        	// set position to relative to keep blocks relative to scrollbar when scrolling
			block.css("top", "").css("left", "")
				.css("position", "relative").css("margin-left", offset + "px");
        }
        
        return {
        	apply: apply	
        };
	}

    var ArrayUtils = {
	
        contains: function (arr, obj) {
            var has = false;
            for (var i = 0; i < arr.length; i++) { if (arr[i] == obj) { has = true; } }
            return has;
        }
    };

    var DateUtils = {
    	
        daysBetween: function (start, end) {
			if(typeof start == "string"){ start = Date.parse(start);}
			if(typeof end == "string"){end = Date.parse(end);}
			
            if (!start || !end) { return 0; }
           // start = Date.parse(start); end = Date.parse(end);
            if (start.getFullYear() == 1901 || end.getFullYear() == 8099) { return 0; }
            var count = 0, date = start.clone();
            while (date.compareTo(end) == -1) { count = count + 1; date.addDays(1); }
            return count;
        },
        
        isWeekend: function (date) {
            return date.getDay() % 6 == 0;
        },

		getBoundaryDatesFromData: function (data, minDays) {
			var minStart = new Date(); maxEnd = new Date();
			
			minStart = Date.parse(data[0].start);
			maxEnd = Date.parse(data[0].end);
			
			for (var i = 1; i < data.length; i++) { // iterate through all data
				
				var start = Date.parse(data[i].start);
				var end = Date.parse(data[i].end);
				
				if(minStart.compareTo(start)==1){minStart = start;}
				if(maxEnd.compareTo(end) == -1){maxEnd = end;}
			}
			
			// Insure that the width of the chart is at least the slide width to avoid empty
			// whitespace to the right of the grid
			if (DateUtils.daysBetween(minStart, maxEnd) < minDays) {
				maxEnd = minStart.clone().addDays(minDays);
			}
			
			return [minStart, maxEnd];
		}
    };

})(jQuery);